/* 

==========================================================

DX490a - Summer 2010

Instructor: Stelios Manousakis

==========================================================

Class 14.1:

Patterns

Contents:

• General / about

- Pattern vs Routine

- Some advantages and disadvantages of Patterns

• Some Pattern classes and other issues

- Deterministic patterns

- Randomness and probability

- Math operations on patterns

- Pattern nesting 

- Filter patterns

- Pfunc

• Patterns in non-real time (NRT)

• Patterns in real time (RT)

- Event

- Pbind and EventStreams

- Using your own SynthDefs 

- Pchain

- Polyphony

- Bonus: Envs as Patterns

• Additional Pattern quarks

• More references

==========================================================

*/




// ================= PATTERNS =================

// (Some of the content below has been compiled and/or rephrased from James Harkins' detailed Practical (and lengthy) Guide to Patterns, included with SC: PG_01_Introduction. This long and well written tutorial is a must read if you intend to use Patterns...





// ====== GENERAL / ABOUT ======

// You can think of Patterns as the language-side equivalent of Demand UGens, which we saw a few classes ago (Class10.1).


// Patterns allow you to create multiple streams from a single specification - kind of like a mold for streams: whereas Patterns define behavior, streams execute it. Patterns usually have compound names, starting with the letter 'P' and then a discription of their functionality. As they are but molds, they only work once placed into a Stream: you have to send them the .asStream or .embedInStream message, otherwise they will NOT work. 


// Here is how you can get multiple streams from one Pattern:

(

var a, b, c;

a = Prand([0, 1, 2], inf);

b = a.asStream;

c = a.asStream;

b.nextN(10).postln;

c.nextN(10);

)




// ------ Pattern vs Routine --


// Patterns are high-level representations of computational tasks; they can be convenient to use, but at the same time confusing as to what exactly they do, hiding their functionality behind a magic veil. With patterns, one writes the result (what is supposed to happen) rather than the process (how to accomplish something).

// Patterns can generate any kind of object, but are most commonly used for creating sequences of numbers, and most people use them for real-time processes, taking advantage of their 'lazy' implementation (they produce a value only when you ask them to).

// Overall, patterns are compact and very good at iterating through data, but cannot really have the internal complexity, nor the customizability of Routines and Tasks. 


// Here is an example of a simple counter with a Routine:

(

r = Routine {

inf.do{|i|

i = i + 1;

i.yield};

};

)

r.next;

r.next;

// or, let's just get the first ten elements together:

r.reset; // reset from before

r.nextN(10);

// The above is same as this:

r.reset;

10.collect({r.next})

// or shorter:

(

r = Routine {

(0..).do{|i|

i.yield};

};

)

r.nextN(10);


// The same process can be simply written as a Pseries Pattern like this:

p = Pseries(start: 0, step: 1, length: inf).asStream;

p.nextN(10);

// or simply:

p = Pseries().asStream;

p.nextN(10);




// ------ Some advantages and disadvantages of Patterns --

/* Pros

- compact

- no need to debug internal functionality (as opposed to a Routine you create yourself)

- no need to decipher how it works once you know what it does

- there is a lot of them

- great for pattern-based music

*/

/* Cons

- more new stuff to learn - and it's a lot of it!

- if there is not a Pattern that does what you want, you'll either have to combine several patterns, or write your own with a Proutine, which practically means having to go back to Routines...

- fairly static, can't change too much in real-time

*/





// ====== SOME PATTERN CLASSES AND OTHER ISSUES ======


// As mentioned above, Patterns are in many aspects a parallel to Demand UGens, something that becomes also obvious with the naming and functionality of several Pattern classes. Some non-exhaustive examples follow:




// ------ Deterministic patterns --


// • Pseries(start, step, length): Arithmetic series (addition).

p = Pseries(0, 1, 8);

p.asStream.nextN(10);

// You can call the entire pattern until it runs out of values

p.asStream.all


// • Pgeom(start, grow, length): Geometric series (multiplication).

p = Pgeom(1, 1.1, 9);

p.asStream.nextN(10);


// • Pseq(list, repeats, offset): Play through the entire list 'repeats' times. Like list.do.

// loop through an array, n times:

p = Pseq([0, 1, 2, 3], inf) // repeat infinite times

p.asStream.nextN(10);


// • Pser(list, repeats, offset): Play through the list as many times as needed, but output only 'repeats' items.

p = Pser([0, 1, 2, 3], 6, 2)

p.asStream.nextN(10);


// • Pslide(list, repeats, len, step, start, wrapAtEnd): Play overlapping segments from the list.

p = Pslide([1, 2, 3, 4, 5], 6, 3, 1, 0);

p.asStream.nextN(10);




// ------ Randomness and probability --


// Random choice from a list of values:

// • Prand(list, repeats): Choose items from the list randomly (same as list.choose)

p = Prand([0, 1, 2, 3], inf); // repeat infinite times

p.asStream.nextN(10);


// • Pxrand(list, repeats): Choose randomly, but never repeat the same item twice

p = Pxrand([0, 1, 2, 3], inf) // repeat infinite times

p.asStream.nextN(10);


// • Pwrand(list, weights, repeats): Choose randomly by weighted probabilities (like list.wchoose(weights))

p = Pwrand([0, 1, 2, 3], [5, 1, 1, 3].normalizeSum, inf) // weights must sum-up to 1

p.asStream.nextN(10);


// • Pshuf(list, repeats): Shuffle the list in random order, and use the same random order 'repeats' times. Like list.scramble

p = Pshuf([0, 1, 2, 3], 2);

p.asStream.nextN(10);



// Random number generators:

// • Pwhite(lo, hi, length): Random numbers, equal distribution ("white noise"). Like rrand(lo, hi)

p = Pwhite(0, 1.0, 6);

p.asStream.nextN(10);


// • Pbrown(lo, hi, step, length): Brownian motion, arithmetic scale (addition)

p = Pbrown(0, 1.0, 0.1, 6);

p.asStream.nextN(10);


// • Plprand(lo, hi, length): Returns the lesser of two equal-distribution random numbers.

p = Plprand(0, 1.0, 6);

p.asStream.nextN(10);


// • Pmeanrand(lo, hi, length): Returns the average of two equal-distribution random numbers, i.e., (x +  y) / 2.

p = Pmeanrand(0, 1.0, 6);

p.asStream.nextN(10);


// • Pbeta(lo, hi, prob1, prob2, length): Beta distribution, where prob1 = α (alpha) and prob2 = β (beta).

p = Pbeta(0, 1.0, 0.1, 0.9, 6);

p.asStream.nextN(10);


p = Ppoisson(4.0, 6);

p.asStream.nextN(10);


// Random patterns can be seeded with Pseed(seed, pattern)

p = Pseed(23, Plprand(0, 1.0, 6));

p.asStream.nextN(10);




// ------ Math operations on patterns --

// You can use math operation and conversions that you use with Arrays and SimpleNumbers in streams


// Simple math

p = Pseries(0, 1, 8) * 100;

p.asStream.nextN(10);


// Can also do arrayed math

p = Pseries(0, 1, 8) * [10, 100];

p.asStream.nextN(10);


// and conversion methods

p = Pseq([57, 58, 59, 69].midicps, inf) // this is infinite times

p.asStream.nextN(10);




// ------ Pattern nesting --


// Again, similarly to Demand UGens, you can nest patterns within patterns within patterns, etc, as arguments, creating structures of increasing complexity. 

(

p = Pseq(

[Pseq([200, 150, 300, 250, 200], 2), // steps 1 - 10; steps 1-5 are the same as 6-10

600, // step 11, always the same

500, // step 12, always the same

Prand([125, 250, 500, 750, 1000])],inf); // step 13 is random

p.asStream.nextN(26);

)




// ------ Filter patterns --


// There are patterns whose job is to modify the output of other patterns - essentially acting as filters. For example:


// • Pn(pattern, repeats): Embeds the source pattern 'repeats' times: simple repetition. This also works on single values: Pn(1, 5) outputs the number 1 5 times.

p = Pn(Pseq([1, 2, 3], 1), 4); // repeat pattern four times

p.asStream.nextN(15)


// • Pstutter(n, pattern): 'n' and 'pattern' are both patterns. Each value from 'pattern' is repeated 'n' times. If 'n' is a pattern, each value can be repeated a different number of times.

p = Pstutter(3, Pseq([1, 2, 3], 1));

p.asStream.nextN(10)


// There is quite a few more, but most have to do with timing and creating parallel streams (more on that later)




// ------ Pfunc --


// Pfunc(nextFunc, resetFunc), is a Pattern whose function you can define yourself. It also can contain a function to be evaluated on reset. 

// There is also Pfuncn (same, but stops after 'repeats' times instead of providing a reset function) and Proutine.

p = Pfunc({rrand(0, 1.0)}, {"reset".postln})

b = p.asStream; // turn the stream into a variable, so that we can reset it

b.nextN(10)

b.reset

b.next


// Something  a bit more complex

(

i = 1;

p = Pfunc(

{// the function

i = i * rrand(0.1, 10);

(i > 10).if ({b.reset}); // reset the stream if 'i' goes above 10

i // function returns 'i' 

},

{// the reset function

i = 0.01

}

);

)

b = p.asStream; // turn the stream into a variable, so that we can reset it

b.nextN(30)

b.reset

b.next





// ====== PATTERNS IN NON-REAL TIME (NRT) ======

// This is definitely not the most common way to use patterns, but they can be quite effective as score generators - and this is a good way to view their functionality with a work paradigm that you are familiar with from DX461/2/3.

 

// Here is a slightly modified version of a quick-and-dirty example by Josh on how to use patterns to populate a CtkScore:

(

var score, note, pitch, dur, now, thisDur;

score = CtkScore.new;

note = CtkSynthDef(\test, {arg freq, dur;

var env;

env = EnvGen.kr(Env([0, 1, 0], [0.05, dur-0.05], [2, -2]));

Out.ar(0, SinOsc.ar(freq, 0, 0.1 * env))

});


pitch = Pseq([\a4, \cs5, \e5, \c5, \a4, \e4, \gs4], inf).asStream;

dur = Pseq(2/[4, 8, 8, 4, 4, 8], inf).asStream;

now = 0;

25.do({

thisDur = dur.next;

score.add(note.new(now, thisDur).freq_(pitch.next.hertz).dur_(thisDur));

now = now + (thisDur * 0.5)

});


score.play;

)

 




// ====== PATTERNS IN REAL TIME ======


// Now, for the more standard use of patterns: real-time

// Similarily to Tasks and Routines, patterns can also be played, paused, resumed and stopped, with the .play, .pause, .resume and .stop messages, in which case they TempoClock to send control messages to the server.


// Before going further on with playing patterns in real time, we need to talk a bit about Events, to understand better how Patterns actually work.




// ------ Event --

// An Event is an Environment specifying an action to be taken when a .play message has been received. As a Dictionary subclass it typically consists of a collection of key/value pairs which determine what actually happens when the event is .played.

// Empty parentheses create events, making them quite useful as name-space storage: 

a = ()

a.class


// The default class method of Event provides a defaultParentEvent, which - to make a long story short - is mapped to the default SynthDef, allowing you to set its arguments and play it:

().play ; // an instance of the default synth using the default argument settings

// now, set some parameters:

(freq: 555, pan: -1).play; // 555Hz, panned left

// You can change the SynthDef used by changing the key named \instrument:

// Let's pick a new SynthDef first:

a = (instrument: \sine, freq: 333).play; // this is a SynthDef we've used several times in this class, if it's not there, don't worry, we'll see more examples further on

a.free


// Event contains quite a few different keys mapped to the same parameters just with a few conversions, and you'll see those used by Patterns quite frequently. 

a = (instrument: \sine, midinote: 60, amp: -12.dbamp).play

a.free


// Look at the bottom part of the Event helpfile for more key jargon.




// ------ Pbind and EventStreams --


// In order for patterns to work in RT (real-time) every .next value in the stream that they generate needs to be passed to a key of an Event, in its turn passing it to the SynthDef. This would mean defining anew an event every time we get a value from a pattern, which would be quite a hassle. To make this more efficient, there is a shortcut class, Pbind, which is used for playing patterns in real time. What Pbind actually does, is combining several pattern streams into a single event stream. Let's have  a look at an example, and make some sense out of it (taken from PG_03_What_Is_Pbind, which explains things in more detail):


(

p = Pbind(

\degree, Pseq(#[0, 0, 4, 4, 5, 5, 4], 1),

\dur, Pseq(#[0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1], 1)

).asStream; // remember, you have to make a stream out of the pattern before using it

)


p.next(Event.new); // shorter: p.next(())


// Here is what this outputs:

( 'degree': 0, 'dur': 0.5 )

( 'degree': 0, 'dur': 0.5 )

( 'degree': 4, 'dur': 0.5 )

( 'degree': 4, 'dur': 0.5 )


// As you see, Pbind essentially re-maps the keys to new values with every .next



// Let's do the same thing using the .play method to make some sound using the TempoClock:

(

p = Pbind(

\degree, Pseq(#[0, 0, 4, 4, 5, 5, 4], 1),

\dur, Pseq(#[0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1], 1)

).play; // and you'll notice that "an EventStreamPlayer" was posted on the post window instead of "a Routine", meaning that a few extra steps have been taken care of behind the scenes

)


// Same thing, twice as fast:

(

p = Pbind(

\degree, Pseq(#[0, 0, 4, 4, 5, 5, 4], 1),

\dur, Pseq(#[0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1], 1)

).play(TempoClock(2)); // You can set the timing of the .play command in the same way as you do with Routines and Tasks

)



// Now, let's try a few more examples using Patterns in RT with Pbind; this time, using nested patterns to demonstrate how they can produce some quite interesting results (this is the same example as above when talking about nested patterns this time with sound:


(

Pbind(

\freq, Pseq(

[Pseq([200, 150, 300, 250, 200], 2), // steps 1 - 10; steps 1-5 are the same as 6-10

600, // step 11, always the same

500, // step 12, always the same

Prand([125, 250, 500, 750, 1000])],inf) // step 13 is random

).play(TempoClock(3));

)


// Another example:

(

Pbind(

\freq, Pseq([

Prand(

// steps 1 - 10; steps 1-5 are the same as 6-10, but now they can be different with each repetition

[Pseq([200, 150, 300, 250, 200], 2), 

Pshuf([200, 150, 300, 250, 400], 2)]),

600, // step 11, always the same

500, // step 12, always the same

Prand([125, 250, 500, 750, 1000])],inf) // step 13 is random

).play(TempoClock(3));

)



// Yet another example, changing durations as well:

(

Pbind(

\freq, Pseq([

Prand(

// steps 1 - 10; steps 1-5 are the same as 6-10, but now they can be different with each repetition

[Pseq([200, 150, 300, 250, 200], 2), 

Pshuf([200, 150, 300, 250, 400], 2)]),

600, // step 11, always the same

500, // step 12, always the same

Prand([125, 250, 500, 750, 1000])],inf), // step 13 is random

\dur, Pseq([

Pseq([0.5, 0.5, 0.5, 0.5, 0.25, 0.25, 0.5]),

Pshuf([0.5, 0.5, 0.5, 0.5, 0.25, 0.25, 1])], inf)

).play(TempoClock(1.5));

)




// ------ Using your own SynthDefs --

// The examples above where using the 'default' SynthDef. But, as we saw with Event, you can use your own SynthDefs. There are a few guidelines that you need to follow:

/*

• Use .store and .memStore or .add to save the Synth to the SynthDescLib, as this is where patterns will be looking. .load won't do the trick.

• Use the same names that are set as the SynthDefs args as the Pbind keys to actually map a pattern to that argument. 

\freq and \dur only work above because the default synthdef has arguments named as such. 

(Although, as we saw above, Event does have some conversion tricks for 'midi-style' synthdefs, dealing with some conversions for frequency, amplitude and duration.)

• Always have the synth release itself! (ex. with a doneAction in an EnvGen either of a fixed duration, or containing a release node). Patterns will not do that for you

*/


// An example, using a nice Bass synth posted by Thor Magnusson in the sc-users list:

(

SynthDef(\ixibass, {arg out=0, amp=0.3, t_trig=1, freq=100, rq=0.004;

var env, signal;

var rho, theta, b1, b2;

b1 = 1.98 * 0.989999999 * cos(0.09);

b2 = 0.998057.neg;

signal = SOS.ar(K2A.ar(t_trig), 0.123, 0.0, 0.0, b1, b2);

signal = RHPF.ar(signal, freq, rq) + RHPF.ar(signal, freq*0.5, rq);

signal = Decay2.ar(signal, 0.4, 0.3, signal);

DetectSilence.ar(signal, 0.01, doneAction:2);

Out.ar(out, signal*(amp*0.45)!2);

}).add;

)


// now, play something:

(

p = Pbind(

\instrument, \ixibass, 

\freq, Pseq([

Prand(

// steps 1 - 10; steps 1-5 are the same as 6-10, but now they can be different with each repetition

[Pseq([200, 150, 300, 250, 200], 2), 

Pshuf([200, 150, 300, 250, 400], 2)]),

600, // step 11, always the same

500, // step 12, always the same

Prand([125, 250, 500, 750, 1000])], inf), // step 13 is random

\dur, Pseq([

Pseq([0.5, 0.5, 0.5, 0.5, 0.25, 0.25, 0.5]),

Pshuf([0.5, 0.5, 0.5, 0.5, 0.25, 0.25, 1])], inf),

\rq, Pseq([

Pbrown(0.0001, 0.02, 7),

Plprand(0.0001, 0.3, 7)], inf),

\amp, Pmeanrand(0.2, 0.4, inf)

).play(TempoClock(1.5));

)

p.stop


// Also have a look at Pmono and PmonoArtic, which are monophonic Pbind variants.




// ------ Pchain --

// It can be very handy and more meaningful to split argument definitions into different sets of patterns, and then arbitrarily bind them together with Pbind, instead of defining everything within one block. 


(

~mel = Pbind(

\freq, Pseq([

Prand(

// steps 1 - 10; steps 1-5 are the same as 6-10, but now they can be different with each repetition

[Pseq([200, 150, 300, 250, 200], 2), 

Pshuf([200, 150, 300, 250, 400], 2)]),

600, // step 11, always the same

500, // step 12, always the same

Prand([125, 250, 500, 750, 1000])],inf)); // step 13 is random


~mel2 = Pbind(

\midinote, Pseq([ 

Plprand(40, 80, 7),

Pbrown(48, 72, 7)], inf));


~durs = Pbind( 

\dur, Pseq([

Pseq([0.5, 0.5, 0.5, 0.5, 0.25, 0.25, 0.5]),

Pshuf([0.5, 0.5, 0.5, 0.5, 0.25, 0.25, 1])], inf));

)


// use the first melody

p = Pchain(~mel, ~durs).play(TempoClock(1.5));

p.stop


// or, use the second melody:

p = Pchain(~mel2, ~durs).play(TempoClock(1.5));

p.stop


// It is also possible to swap Pbinds on the fly: see PG_06c_Composition_of_Patterns



// ------ Polyphony --

// You can combine patterns into a single parallel stream creating polyphonic structures that can be started and stopped as a single unit. There are a few different patterns to do that, with the most common one being Ppar.


// Here is a polyphonic example from Josh Parmenter and Pete Moss; note that Ppars can be nested, similarily to other patterns:


(

Ppar([

Pbind(

\dur, Prand([0.2, 0.4, 0.6], inf), 

\midinote, Prand([72, 74, 76, 77, 79, 81], inf),

\db, -26,

\legato, 1.1

),

Pseq([

Pbind(

\dur, 3.2,

\freq, Pseq([\rest])),

Prand([

Ppar([

Pbind(

\dur, 0.2, 

\pan,  0.5, 

\midinote, Pseq([60, 64, 67, 64])),

Pbind(

\dur, 0.4, 

\pan, -0.5, 

\midinote, Pseq([48, 43]))

]),

Ppar([

Pbind(

\dur, 0.2, 

\pan,  0.5, 

\midinote, Pseq([62, 65, 69, 65])),

Pbind(

\dur, 0.4, 

\pan, -0.5, 

\midinote, Pseq([50, 45]))

]),

Ppar([

Pbind(

\dur, 0.2, 

\pan,  0.5, 

\midinote, Pseq([64, 67, 71, 67])),

Pbind(

\dur, 0.4, 

\pan, -0.5, 

\midinote, Pseq([52, 47]))

])

], 12)

], inf)

], inf).play(TempoClock(1));

)


// Also, have a look at an SC rendering of a classic, Kraftwerk's 'Spacelab' for the use of Ppar and patterns in general (this is included in the SC distribution):

Document.open("examples/pieces/spacelab.scd")


// For more, see: PG_06d_Parallel_Patterns




// ------ Bonus: Envs as Patterns -- 


// Finally, here' s a bonus for you:

// Env contains the .asStream method, so you can use it as a stream using the .next message. This is similar to the array-style indexing you are familiar with:

e = Env([0, 1, 1, 0], [1, 1, 1]);

e.at(2); // == 1

e[2.5]; // == 0.5


// When Env is converted to a stream, it will return the value at the specified elapsed time

(

r = fork {

e = Env([0, 1, 1, 0], [1, 1, 1]).asStream;

40.do{

e.next.postln;

0.25.wait; // elapsed time

}

}

)


// When done, the stream will keep polling the last value of the envelope. You can use the Pif class to make it either stop, or loop (see PG_06b_Time_Based_Patterns)


// Here is a quick example from Harkins' tutorial, without getting too much into the details:

(

p = Pbind(

\degree, Pwhite(-4, 11, inf),

\pan, Pn(Pif(Ptime(inf) <= 4.0, Env(#[-1, 1, -1], #[2, 2], \sin)), inf),

\dur, 0.125

).play;

)





// ====== ADDITIONAL PATTERN QUARKS ======

// You may want to check out Harkins' ddwPatterns quark, which contains some more pattern tools:

Quarks.install( "ddwPatterns", checkoutIfNeeded: false)


// Also, redFrik has a quark with a library of patterns acting as UGens, called UGenPatterns:

Quarks.install( "UGenPatterns", checkoutIfNeeded: false)





// ====== MORE REFERENCES ======


/*

Patterns are an entire world in SuperCollider. This was only meant as an introduction, and to ease the pain of delving into them, if you so wish. For more information look here:


[Pattern]

[A Practical Guide to Patterns] -  (James Harkins' tutorial)

[Streams-Patterns-Events2 - 7]  -  (a bit older tutorial on patterns)

[Streams] an overview of the whole Pattern and Stream system!

*/